package com.hp.snap.gr.db.voltdb;

import com.google.gson.Gson;
import com.hp.snap.gr.db.voltdb.sp.StatisticsDRResponses;
import com.hp.snap.gr.db.voltdb.sp.VoltDBTable;
import com.hp.snap.gr.exception.ActionException;
import com.hp.snap.gr.exception.DatabaseException;
import com.hp.snap.gr.model.DRAgent;
import com.hp.snap.gr.model.Database;
import com.hp.snap.gr.model.Server;
import com.hp.snap.gr.utils.*;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.*;

public class ControlDRAgent {
    private static final Logger logger = LoggerFactory.getLogger(ControlDRAgent.class);

    public static boolean isDRAgentEnabled(int replicaPort, boolean isRemote) {

        String command = "test \"$(ps -aef | grep VoltDBReplicationAgent | grep -v grep | grep " + replicaPort + ")\" != \"\"";

        int nRes = -1;
        if (logger.isDebugEnabled()) {
            logger.debug("[isDRAgentEnabled] try to check DR status on "
                    + (isRemote ? "remote site" : "local site") + " with remoteReplicaPort=" + replicaPort);
        }
        try {
            if (logger.isDebugEnabled()) {
                logger.debug("[isDRAgentEnabled] check db status command = {}", command);
            }
            if (!isRemote) {
                nRes = CmdUtils.runShellScript(command);
            } else {
                nRes = isRemoteDRAgentEnabled(replicaPort, command);
            }
            if (nRes != 0) {
                if (logger.isTraceEnabled()) {
                    logger.trace("dragent is not running");
                }
                return false;
            }

            if (logger.isDebugEnabled()) {
                logger.debug("[isDRAgentEnabled] check dragent process with exit code {}", nRes);
            }
            return true;
        } catch (IOException ex) {
            logger.warn("IOException:" + ex.getLocalizedMessage());
        } catch (Exception ex) {
            logger.warn("Exception:" + ex.getLocalizedMessage());
        }

        return nRes == 0;
    }

    private static int isRemoteDRAgentEnabled(int remoteReplicaPort, String command) {
        String correspondSiteName = Utils.getCorrespondSiteName(GRMConfigManager.getSiteName());
        String sshPath = Constants.SSH_PATH;
        String _sshUser = Utils.getConfigurationValue(Constants.CFG_GRG_ROOT + "/" + correspondSiteName, Constants.CFG_GRG_SITE_SSH_USER);
        String _keyFile = sshPath + File.separator + Constants.SSH_KEY_FILE;
        String _IP = Utils.getGRHostIP(correspondSiteName);

        if (logger.isDebugEnabled()) {
            logger.debug("check dr status on remote host:{}", _IP);
        }
        SSHTools ssh = new SSHTools(_sshUser, _keyFile);
        String result = "";
        int status = -1;
        try {
            result = ssh.executeRemoteShell(false, command, _IP);
            status = ssh.getExitStatusOfCommandResult(result);
            if (logger.isDebugEnabled()) {
                logger.debug("execute the remote shell command status is:{}", status);
            }
            String resultStr = ssh.getCommandOutput(result);
            if (logger.isDebugEnabled()) {
                logger.debug("execute the remote shell command output is:{}", resultStr);
            }
            String errStr = ssh.getCommandErr(result);
            if (logger.isDebugEnabled()) {
                logger.debug("execute the remote shell command error is:{}", errStr);
            }
        } catch (Exception e) {
            logger.error(e.toString());
        }
        if (logger.isDebugEnabled()) {
            logger.debug("execute the remote shell command result is:{}", result);
        }
        ssh.closeAll();

        return status;
    }

    public static DRAgent getDRAgentStatusInfo(String dbInstanceName, List<VoltDBClientConfig> configs, boolean isRemote) throws DatabaseException {
        if (logger.isTraceEnabled()) {
            logger.trace("[getDRAgentStatusInfo] input dbInstanceName={}", dbInstanceName);
        }

        DRAgent drAgentInfo = new DRAgent();
        String runingStatus = "NA";

        if (configs != null) {
            JSONHttpVoltDBClientFactory factory = new JSONHttpVoltDBClientFactory(configs);
            List<Object> params = Arrays.asList("DR", (Object) 1);
            JSONObject response = factory.invokeStoredProcedure("@Statistics", params, false/*not throw exception*/);

            if (response == null) {
                logger.warn("[getDRAgentStatusInfo] cannot get json response from DR tables of voltDB '@Statistics' procedure");

            } else {
                if (logger.isTraceEnabled()) {
                    logger.trace("[getDRAgentStatusInfo] request dbInstanceName={} with DR @Statistic procedure got response={}",
                            new Object[]{dbInstanceName, new Gson().toJson(response, JSONObject.class)});
                }
            }

            JSONObject streams = response != null ? StatisticsDRResponses.getReplicationStreams(response) : null;
            JSONObject snapshots = response != null ? StatisticsDRResponses.getReplicationSnapshot(response) : null;

            if (logger.isTraceEnabled()) {
                logger.trace("[getDRAgentStatusInfo] streams={} snapshots={}",
                        new Object[]{new Gson().toJson(streams, JSONObject.class), new Gson().toJson(snapshots, JSONObject.class)});
            }

            int unFinishedTransactionSize = streams != null ? StatisticsDRResponses.getSyncNotFinishedCount(streams) : 0;
            VoltDBTable snapshotTable = snapshots != null ? StatisticsDRResponses.getReplicatingSnapshot(snapshots) : null;
            int unFinishedSnapshootSize = snapshotTable != null ? snapshotTable.getRows().size() : 0;
            drAgentInfo.setUnFinishedSnapshootSize(unFinishedSnapshootSize);
            drAgentInfo.setUnFinishedTransactionSize(unFinishedTransactionSize);

            if (logger.isTraceEnabled()) {
                logger.trace("[getDRAgentStatusInfo] unFinishedTransactionSize={} unFinishedSnapshootSize={}",
                        new Object[]{unFinishedTransactionSize, unFinishedSnapshootSize});
            }

        }

        // get remote site database profiles
        Database database = null;
        try {
            database = RESTfulUtils.getVoltDBDatabase(dbInstanceName);
        } catch (Exception e) {
            logger.warn("Cannot get information from VEM of " + GRMConfigManager.getSiteName() + ":" + dbInstanceName);
            if (logger.isDebugEnabled()) {
                logger.debug(Utils.stackTraceToString(e));
            }
        }
        if (database != null) {
            boolean isRuning = isDRAgentEnabled(isRemote?database.getReplicationport():database.getPort(), isRemote);
            runingStatus = isRuning ? "RUNNING" : "STOPPED";
            if (logger.isTraceEnabled()) {
                logger.trace("[getDRAgentStatusInfo] isRuning={} ", isRuning);
            }
        }
        drAgentInfo.setDrAgentRunningStatus(runingStatus);
        String logfilePath = Constants.USER_DIR + "/logs/dragent." + dbInstanceName + ".log";
        drAgentInfo.setLatestDRAgentLog(getFileContentsFromLastByLines(logfilePath, 5)
                + configs == null ? "\n" + Constants.DR_AGENT_NOT_AVAILABLE : null);
        return drAgentInfo;
    }

    private static String getFileContentsFromLastByLines(String filePathName, int linesToRead) {
        if (logger.isTraceEnabled()) {
            logger.trace("[getFileContentsFromLastByLines] input parameters filePathName={} linesToRead={}", filePathName, linesToRead);
        }
        StringBuffer logContent = null;
        File f = new File(filePathName);

        if (!f.isFile()) {
            logger.warn("Cannot find file " + filePathName);
            return null;
        }

        String command = "tail -" + linesToRead + " " + filePathName;
        CmdResult result = new CmdResult();
        try {
            int rs = CmdUtils.runShellScript(command, result);
            if (rs != 0) {
                throw new ActionException("command execute with exit code " + rs);
            }
            logContent = result.getStdout();
        } catch (IOException ex) {
            logger.warn("IOException:" + ex.getLocalizedMessage());
        } catch (Exception ex) {
            logger.warn("Exception:" + ex.getLocalizedMessage());
        }

        if (logger.isDebugEnabled()) {
            logger.debug("[getFileContentsFromLastByLines] get contents={} from  log file={}", logContent, filePathName);
        }
        return logContent !=null ? logContent.toString() : null;
    }

    public static boolean stopDRAgent(int replicaPort) {

        String command = "kill $(ps -ef | grep \"VoltDBReplicationAgent\" | grep " + replicaPort + " | awk '{ print $2 }')";

        int nRes;
        if (logger.isDebugEnabled()) {
            logger.debug("[stopDRAgent] try to stop DR agent");
        }
        try {
            if (logger.isDebugEnabled()) {
                logger.debug("[stopDRAgent] stop db command = {}", command);
            }
            nRes = CmdUtils.runShellScript(command);
            if (logger.isDebugEnabled()) {
                logger.debug("[stopDRAgent] stop DR agent successfully with exit code {}", nRes);
            }
            return true;
        } catch (IOException ex) {
            logger.warn("IOException:" + ex.getLocalizedMessage());
        } catch (Exception ex) {
            logger.warn("Exception:" + ex.getLocalizedMessage());
        }

        return false;
    }

    public static boolean startDRAgent(String dbInstanceName, String masterHost, String masterPort,
                                       String slaveHost, String slavePort, String dbUser, String dbPwd) throws DatabaseException {
        String dragentPath;
        String vdbHome = GRMConfigManager.getEIUMHome() + "/VoltDB";
        dragentPath = vdbHome + "/bin/dragent";
        if (logger.isTraceEnabled()) {
            logger.trace("[startDRAgent] dragent home = " + dragentPath);
        }
        File f = new File(dragentPath);
        if (!f.exists()) {
            throw new DatabaseException("Can not get 'dragent' tools on server");
        }
        String command = Constants.USER_GR_ARTIFACT + File.separator + "StartDragent.sh " + GRMConfigManager.getEIUMHome() + " " +
                masterHost + " " + masterPort + " " + slaveHost + " " + slavePort + " " + dbUser + " " + dbPwd + " " + dbInstanceName;
        int nRes = -1;
        if (logger.isDebugEnabled()) {
            logger.debug("[startDRAgent] try to start DR agent");
        }
        try {
            nRes = CmdUtils.runShellScript(command);
            if (nRes == 0 && logger.isDebugEnabled()) {
                logger.debug("[startDRAgent] start DR agent successfully");
            }
        } catch (IOException ex) {
            logger.warn("IOException:" + ex.getLocalizedMessage());
        } catch (Exception ex) {
            logger.warn("Exception:" + ex.getLocalizedMessage());
        }

        return nRes <= 0;
    }

    public static List<VoltDBClientConfig> getVoltDBClientConfig(Database database, String siteName) throws ActionException {
        if (logger.isDebugEnabled()) {
            logger.debug("[getVoltDBClientConfig] database={} siteName={}", new Gson().toJson(database, Database.class), siteName);
        }

        List<VoltDBClientConfig> dbClientConfig = new ArrayList<VoltDBClientConfig>();
        String voltDBNodePath = Constants.CFG_GRG_ROOT + "/" + siteName + "/" + Constants.CFG_GRG_VOLTDB;
        String vdbRestRoot = Utils.getConfigurationValue(voltDBNodePath, Constants.CFG_GRG_VDB_REST_ROOT);

        if (logger.isDebugEnabled()) {
            logger.debug("[getVoltDBClientConfig] voltDBNodePath={} vdbRestRoot={}", voltDBNodePath, vdbRestRoot);
        }

        if (StringUtils.isEmpty(vdbRestRoot)) {
            vdbRestRoot = Constants.VOLTDB_SP_URI_PREFIX;
        }

        String dbNodePath = voltDBNodePath + "/" + database.getName();
        String dbUser = Utils.getConfigurationValue(dbNodePath, Constants.CFG_GRG_VOLTDB_DB_USER);
        String dbPwd = Utils.getConfigurationValue(dbNodePath, Constants.CFG_GRG_VOLTDB_DB_PWD);
        int dbHttpPort = RESTfulUtils.getVoltDBDeployment(siteName, database.getName()).getHttpport();

        List<Server> servers = database.getServers();

        for (Server server : servers) {
            if (Constants.VDB_STATUS_ONLINE.equals(server.getStatus())) {
                String hostIp = Utils.getConfigurationValue(Constants.CFG_GRG_ROOT + "/" + siteName + "/HOST/" + server.getHost(), Constants.CFG_GRG_HOST_IP);
                if (hostIp == null || hostIp.equals("")) {
                    hostIp = server.getHost();
                }
                String url = "http://" + hostIp + ":" + dbHttpPort + vdbRestRoot;
                if (logger.isDebugEnabled()) {
                    logger.debug("[getVoltDBClientConfig] url={}, dbUser={}, dbPwd=******", new Object[]{url, dbUser, dbPwd});
                }

                VoltDBClientConfig config = new VoltDBClientConfig(url, dbUser, dbPwd);
                dbClientConfig.add(config);
            }
        }

        if (dbClientConfig.isEmpty()) {
            throw new ActionException("No available server for DB: " + database.getName());
        }

        return dbClientConfig;
    }

    public static Map<String, List<VoltDBClientConfig>> getVoltDBClientConfig(List<Database> databases, String siteName) throws ActionException {
        Map<String, List<VoltDBClientConfig>> dbClientConfigs = new HashMap<String, List<VoltDBClientConfig>>();
        for (Database database : databases) {
            dbClientConfigs.put(database.getName(), getVoltDBClientConfig(database, siteName));
        }
        return dbClientConfigs;
    }
}
